package com.megvii.corelib.net;

import android.content.Context;

import com.android.volley.AuthFailureError;
import com.android.volley.DefaultRetryPolicy;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.toolbox.JsonRequest;
import com.android.volley.toolbox.Volley;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.megvii.corelib.net.request.MegviiRequest;
import com.squareup.okhttp.OkHttpClient;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Created by linyue on 15/12/18.
 */
public class MegviiHttpManager implements Response.Listener, MegviiHeaderListener
{
    private String urlBase;

    private FunctionIndicator indicator;

    private Context context;

    private OkHttpClient httpClient;

    private RequestQueue queue;

    public MegviiHttpManager(Context context)
    {
        this.indicator = new FunctionIndicator();
        this.context = context;
        this.httpClient = new OkHttpClient();
        this.queue = Volley.newRequestQueue(context, new OKHttpStack(httpClient));
    }

    /**
     * 通过request的中标有{@link RequestParam}的成员变量生成http request的参数
     *
     * @param request 自定义的请求，{@link MegviiRequest}的派生类
     * @return
     */
    private static Map<String, String> getParams(MegviiRequest request)
    {
        HashMap<String, String> params = new HashMap<>();
        Field[] fields = request.getClass().getFields();
        for (Field field : fields)
        {
            RequestParam annotation = field.getAnnotation(RequestParam.class);
            if (annotation != null)
            {
                String key = null;
                if (annotation.name() != null && !annotation.name().equals(""))
                {
                    key = annotation.name();
                }
                else
                {
                    key = field.getName();
                }
                String value = null;
                boolean accessible = field.isAccessible();
                if (!accessible)
                {
                    field.setAccessible(true);
                }
                try
                {
                    Object fieldValue = field.get(request);
                    if (fieldValue != null)
                    {
                        value = String.valueOf(fieldValue);
                    }
                }
                catch (IllegalAccessException e)
                {
                    e.printStackTrace();
                }
                if (!accessible)
                {
                    field.setAccessible(false);
                }
                //
                if (value != null)
                {
                    params.put(key, value);
                }
            }
        }
        return params;
    }

    public static <T> Map<String, T> getMultipartParams(Object request, Class<T> clazz)
    {
        HashMap<String, T> params = new HashMap<>();
        Field[] fields = request.getClass().getFields();
        for (Field field : fields)
        {
            RequestParam annotation = field.getAnnotation(RequestParam.class);
            if (annotation != null && field.getType().equals(clazz))
            {
                String key = null;
                if (annotation.name() != null && !annotation.name().equals(""))
                {
                    key = annotation.name();
                }
                else
                {
                    key = field.getName();
                }
                T value = null;
                boolean accessible = field.isAccessible();
                if (!accessible)
                {
                    field.setAccessible(true);
                }
                try
                {
                    Object fieldValue = field.get(request);
                    if (fieldValue != null)
                    {
                        value = (T) fieldValue;
                    }
                }
                catch (IllegalAccessException e)
                {
                    e.printStackTrace();
                }
                if (!accessible)
                {
                    field.setAccessible(false);
                }
                //
                if (value != null)
                {
                    params.put(key, value);
                }
            }
        }
        return params;
    }

    public void release()
    {
        this.queue.stop();
    }

    public void register(Object listener)
    {
        indicator.register(listener);
    }

    public void unregister(Object listener)
    {
        indicator.unregister(listener);
    }

    /**
     * 通过{@link MegviiRequest}生成volley的{@link JsonRequest}对象
     *
     * @param method           {@link com.android.volley.Request.Method}中的常量
     * @param request          自定义的请求，{@link MegviiRequest}的派生类
     * @param responseClass    Http Response中数据转换成的数据对象的类型
     * @param callbackInstance 处理Http Response的回调类
     * @param errorListener
     * @return
     */
    public void requestJson(int method, MegviiRequest request, Class<?> responseClass,
            Object callbackInstance, Response.ErrorListener errorListener)
    {
        MegviiJsonRequest jsonRequest = createJsonRequest(method, request, responseClass,
                callbackInstance, errorListener);
        jsonRequest.setHeaderListener(this);
        queue.add(jsonRequest);
    }

    public void requestMultipart(int method, MegviiRequest request, Class<?> responseClass,
            Object callbackInstance, Response.ErrorListener errorListener)
    {
        MegviiMultipartRequest multipartnRequest = createMultipartRequest(method, request,
                responseClass, callbackInstance, errorListener);
        multipartnRequest.setHeaderListener(this);
        queue.add(multipartnRequest);
    }

    public void requestPureJson(int method, MegviiRequest request, Class<?> responseClass,
            final Object callbackInstance, Response.ErrorListener errorListener)
    {
        String json = "";
        ObjectMapper mapper = new ObjectMapper();
        mapper.configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false);

        try
        {
            json = mapper.writeValueAsString(request);
        }
        catch (JsonProcessingException e)
        {
            e.printStackTrace();
        }
        String url = urlBase + request.getCommand();
        this.register(callbackInstance);
        MegviiPureJsonRequest jsonRequest = new MegviiPureJsonRequest(method, url, json, this,
                errorListener, responseClass)
        {
            @Override
            public Map<String, String> getHeaders() throws AuthFailureError
            {
                return MegviiHttpManager.this.getHeaders();
            }

            @Override
            protected void onFinish()
            {
                super.onFinish();
                MegviiHttpManager.this.unregister(callbackInstance);
            }
        };
        jsonRequest.setHeaderListener(this);
        queue.add(jsonRequest);
    }

    /**
     * 通过{@link MegviiRequest}生成volley的{@link JsonRequest}对象
     *
     * @param method           {@link com.android.volley.Request.Method}中的常量
     * @param request          自定义的请求，{@link MegviiRequest}的派生类
     * @param responseClass    Http Response中数据转换成的数据对象的类型
     * @param callbackInstance 处理Http Response的回调类
     * @param errorListener
     * @return
     */
    protected <T> MegviiJsonRequest<T> createJsonRequest(int method, final MegviiRequest request,
            Class<T> responseClass, final Object callbackInstance,
            Response.ErrorListener errorListener)
    {
        this.register(callbackInstance);
        String url = urlBase + request.getCommand();
        final Map<String, String> params = getParams(request);
        if (method == Request.Method.GET)
        {
            Iterator<String> iterator = params.keySet().iterator();
            int i = 0;
            while (iterator.hasNext())
            {
                String key = iterator.next();
                String value = params.get(key);
                String separator = (i == 0 ? "?" : "&");
                url = url + separator + key + "=" + value;
                i++;
            }
        }
        MegviiJsonRequest<T> jsonRequest = new MegviiJsonRequest<T>(method, url, this,
                errorListener, responseClass)
        {
            @Override
            protected Map<String, String> getParams() throws AuthFailureError
            {
                return params;
            }

            @Override
            public Map<String, String> getHeaders() throws AuthFailureError
            {
                return MegviiHttpManager.this.getHeaders();
            }

            @Override
            protected void onFinish()
            {
                super.onFinish();
                MegviiHttpManager.this.unregister(callbackInstance);
            }
        };
        return jsonRequest;
    }

    protected <T> MegviiMultipartRequest<T> createMultipartRequest(int method,
            final MegviiRequest request, Class<T> responseClass, final Object callbackInstance,
            Response.ErrorListener errorListener)
    {
        this.register(callbackInstance);
        String url = urlBase + request.getCommand();
        final Map<String, String> params = getParams(request);
        MegviiMultipartRequest<T> multipartRequest = new MegviiMultipartRequest<T>(method, url,
                this, errorListener, responseClass)
        {
            @Override
            public Map<String, String> getHeaders() throws AuthFailureError
            {
                return MegviiHttpManager.this.getHeaders();
            }

            @Override
            protected Map<String, String> getStringParams()
            {
                HashMap<String, String> result = new HashMap<>();
                Map<String, String> stringParams = getMultipartParams(request, String.class);
                Map<String, Integer> intParams = getMultipartParams(request, int.class);
                Map<String, Long> longParams = getMultipartParams(request, long.class);
                Map<String, Float> floatParams = getMultipartParams(request, float.class);
                Map<String, Double> doubleParams = getMultipartParams(request, double.class);
                Map<String, Boolean> booleanParams = getMultipartParams(request, boolean.class);
                //TODO boxed type of int long float double boolean...
                result.putAll(stringParams);
                mergeParams(result, intParams);
                mergeParams(result, longParams);
                mergeParams(result, floatParams);
                mergeParams(result, doubleParams);
                mergeParams(result, booleanParams);
                return result;
            }

            @Override
            protected Map<String, byte[]> getByteArrayParams()
            {
                return getMultipartParams(request, byte[].class);
            }

            protected Map<String, String[]> getStringArrayParams()
            {
                HashMap<String, String[]> result = new HashMap<>();
                Map<String, String[]> stringArrayParams = getMultipartParams(request,
                        String[].class);
                Map<String, Integer[]> intArrayParams = getMultipartParams(request,
                        Integer[].class);
                Map<String, Long[]> longArrayParams = getMultipartParams(request, Long[].class);
                Map<String, Float[]> floatArrayParams = getMultipartParams(request, Float[].class);
                Map<String, Double[]> doubleArrayParams = getMultipartParams(request,
                        Double[].class);
                Map<String, Boolean[]> booleanArrayParams = getMultipartParams(request,
                        Boolean[].class);
                result.putAll(stringArrayParams);
                mergeArrayParams(result, intArrayParams);
                mergeArrayParams(result, longArrayParams);
                mergeArrayParams(result, floatArrayParams);
                mergeArrayParams(result, doubleArrayParams);
                mergeArrayParams(result, booleanArrayParams);
                //
                Map<String, int[]> intArrayParams2 = getMultipartParams(request, int[].class);
                Map<String, String[]> boxed = boxMapOfInt(intArrayParams2);
                mergeArrayParams(result, boxed);
                Map<String, long[]> longArrayParams2 = getMultipartParams(request, long[].class);
                boxed = boxMapOfLong(longArrayParams2);
                mergeArrayParams(result, boxed);
                Map<String, float[]> floatArrayParams2 = getMultipartParams(request, float[].class);
                boxed = boxMapOfFloat(floatArrayParams2);
                mergeArrayParams(result, boxed);
                Map<String, double[]> doubleArrayParams2 = getMultipartParams(request,
                        double[].class);
                boxed = boxMapOfDouble(doubleArrayParams2);
                mergeArrayParams(result, boxed);
                Map<String, boolean[]> booleanArrayParams2 = getMultipartParams(request,
                        boolean[].class);
                boxed = boxMapOfBoolean(booleanArrayParams2);
                mergeArrayParams(result, boxed);
                return result;
            }

            private Map<String, String[]> boxMapOfInt(Map<String, int[]> map)
            {
                if (map == null)
                {
                    return null;
                }
                HashMap<String, String[]> result = new HashMap<>();
                Set<Map.Entry<String, int[]>> entrySet = map.entrySet();
                for (Map.Entry<String, int[]> entry : entrySet)
                {
                    String key = entry.getKey();
                    int[] values = entry.getValue();
                    String[] newValues = null;
                    if (values != null)
                    {
                        newValues = new String[values.length];
                        for (int i = 0; i < values.length; i++)
                        {
                            newValues[i] = String.valueOf(values[i]);
                        }
                    }
                    result.put(key, newValues);
                }
                return result;
            }

            private Map<String, String[]> boxMapOfLong(Map<String, long[]> map)
            {
                if (map == null)
                {
                    return null;
                }
                HashMap<String, String[]> result = new HashMap<>();
                Set<Map.Entry<String, long[]>> entrySet = map.entrySet();
                for (Map.Entry<String, long[]> entry : entrySet)
                {
                    String key = entry.getKey();
                    long[] values = entry.getValue();
                    String[] newValues = null;
                    if (values != null)
                    {
                        newValues = new String[values.length];
                        for (int i = 0; i < values.length; i++)
                        {
                            newValues[i] = String.valueOf(values[i]);
                        }
                    }
                    result.put(key, newValues);
                }
                return result;
            }

            private Map<String, String[]> boxMapOfFloat(Map<String, float[]> map)
            {
                if (map == null)
                {
                    return null;
                }
                HashMap<String, String[]> result = new HashMap<>();
                Set<Map.Entry<String, float[]>> entrySet = map.entrySet();
                for (Map.Entry<String, float[]> entry : entrySet)
                {
                    String key = entry.getKey();
                    float[] values = entry.getValue();
                    String[] newValues = null;
                    if (values != null)
                    {
                        newValues = new String[values.length];
                        for (int i = 0; i < values.length; i++)
                        {
                            newValues[i] = String.valueOf(values[i]);
                        }
                    }
                    result.put(key, newValues);
                }
                return result;
            }

            private Map<String, String[]> boxMapOfDouble(Map<String, double[]> map)
            {
                if (map == null)
                {
                    return null;
                }
                HashMap<String, String[]> result = new HashMap<>();
                Set<Map.Entry<String, double[]>> entrySet = map.entrySet();
                for (Map.Entry<String, double[]> entry : entrySet)
                {
                    String key = entry.getKey();
                    double[] values = entry.getValue();
                    String[] newValues = null;
                    if (values != null)
                    {
                        newValues = new String[values.length];
                        for (int i = 0; i < values.length; i++)
                        {
                            newValues[i] = String.valueOf(values[i]);
                        }
                    }
                    result.put(key, newValues);
                }
                return result;
            }

            private Map<String, String[]> boxMapOfBoolean(Map<String, boolean[]> map)
            {
                if (map == null)
                {
                    return null;
                }
                HashMap<String, String[]> result = new HashMap<>();
                Set<Map.Entry<String, boolean[]>> entrySet = map.entrySet();
                for (Map.Entry<String, boolean[]> entry : entrySet)
                {
                    String key = entry.getKey();
                    boolean[] values = entry.getValue();
                    String[] newValues = null;
                    if (values != null)
                    {
                        newValues = new String[values.length];
                        for (int i = 0; i < values.length; i++)
                        {
                            newValues[i] = String.valueOf(values[i]);
                        }
                    }
                    result.put(key, newValues);
                }
                return result;
            }

            private <T> void mergeParams(Map<String, String> result, Map<String, T> newMap)
            {
                for (Map.Entry<String, T> entry : newMap.entrySet())
                {
                    String key = entry.getKey();
                    String value = String.valueOf(entry.getValue());
                    result.put(key, value);
                }
            }

            private <T> void mergeArrayParams(Map<String, String[]> result, Map<String, T[]> newMap)
            {
                for (Map.Entry<String, T[]> entry : newMap.entrySet())
                {
                    String key = entry.getKey();
                    T[] values = entry.getValue();
                    String[] originalValues = result.get(key);
                    int valuesSize = values != null ? values.length : 0;
                    int originalValuesSize = originalValues != null ? originalValues.length : 0;
                    int totleSize = valuesSize + originalValuesSize;
                    String[] mergedValues = new String[totleSize];
                    for (int i = 0; i < originalValuesSize; i++)
                    {
                        mergedValues[i] = originalValues[i];
                    }
                    for (int i = originalValuesSize; i < totleSize; i++)
                    {
                        T value = values[i - originalValuesSize];
                        mergedValues[i] = String.valueOf(value);
                    }
                    result.put(key, mergedValues);
                }
            }

            @Override
            protected void onFinish()
            {
                super.onFinish();
                MegviiHttpManager.this.unregister(callbackInstance);
            }
        };
        multipartRequest.setRetryPolicy(
                new DefaultRetryPolicy(DefaultRetryPolicy.DEFAULT_TIMEOUT_MS * 5,
                        DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
                        DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
        return multipartRequest;
    }

    /**
     * 子类重写这个函数，用来设置统一的http request header
     *
     * @return
     */
    protected Map<String, String> getHeaders()
    {
        return new HashMap<>();
    }

    public void setUrlBase(String urlBase)
    {
        this.urlBase = urlBase;
    }

    public String getUrlBase()
    {
        return urlBase;
    }

    @Override
    public void onResponse(Object response)
    {
        List<FunctionIndicator.IndicateResult> results = indicator.indicate(response.getClass());
        for (FunctionIndicator.IndicateResult result : results)
        {
            boolean accessible = result.method.isAccessible();
            if (!accessible)
            {
                result.method.setAccessible(true);
            }
            try
            {
                result.method.invoke(result.callee, response);
            }
            catch (IllegalAccessException e)
            {
                e.printStackTrace();
            }
            catch (InvocationTargetException e)
            {
                e.printStackTrace();
            }
            if (!accessible)
            {
                result.method.setAccessible(false);
            }
        }
    }

    @Override
    public void onReceiveHeader(String key, String value)
    {

    }
}
